package cn.anecansaitin.industrialization_expansion.common.cap;

import cn.anecansaitin.industrialization_expansion.api.common.cap.IModuleStorage;
import cn.anecansaitin.industrialization_expansion.api.common.item.IToolModule;
import com.ibm.icu.impl.Pair;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.minecraft.block.BlockState;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.util.NonNullList;
import net.minecraftforge.common.ToolType;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fml.common.thread.EffectiveSide;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

/*
 * nbt结构
 *   nbt
 *    └ cap
 *       └ module
 *          ├ size (int)
 *          ├ core (intArray)
 *          └ module (list)
 *             ├ 0 (compound)
 *             │  ├ ┌──────────────┐
 *             │  ├ │   ItemStack  │
 *             │  ├ │ serializeNBT │
 *             │  └ └──────────────┘
 *             ├ 1
 * */
public class ItemModuleStorage implements IModuleStorage {
    protected final ItemStack stack;
    protected NonNullList<ItemStack> modules;
    protected IntArrayList coreFlg = new IntArrayList();
    private Map<ToolType, Integer> toolTypeLevel = new HashMap<>();
    private Set<ToolType> toolTypes = new HashSet<>();
    private boolean disableShield = false;
    private float destroySpeed = 0;
    private int maxDurability = 0;
    private int maxEnergyCapacity = 0;
    private double damage = 0;
    private double attackSpeed = 0;
    private int enchantability = 0;
    private Set<Enchantment> enchantments = new HashSet<>();
    private Set<EnchantmentType> enchantmentTypes = new HashSet<>();
    private boolean fireImmune;
    private float xpRepairRatio = 0;


    public ItemModuleStorage(ItemStack stack) {
        this.stack = stack;
        reload();
    }

    @Override
    public NonNullList<ItemStack> getModules() {
        clientCheck();
        return modules;
    }

    @Override
    public void setModules(NonNullList<ItemStack> modules) {
        this.modules = modules;
        onModuleChanged();
    }

    @Override
    public ItemStack getModule(int index) {
        clientCheck();
        return modules.get(index);
    }

    @Override
    public void setCoreFlg(int index, int core) {
        coreFlg.set(index, core);
        onCoreChanged();
    }

    @Override
    public void setCoreFlgs(IntArrayList flgs) {
        coreFlg = flgs;
        onCoreChanged();
    }

    @Override
    public IntArrayList getCoreFlgs() {
        clientCheck();
        return coreFlg;
    }

    @Override
    public int getSize() {
        clientCheck();
        return getModules().size();
    }

    @Override
    public void setModule(int index, ItemStack module) {
        getModules().set(index, module);
        onModuleChanged(index);
    }

    @Override
    public int getHarvestLevel(@Nonnull ToolType tool, @Nullable PlayerEntity player, @Nullable BlockState blockState) {
        clientCheck();
        return toolTypeLevel.getOrDefault(tool, 0);
    }

    @Nonnull
    @Override
    public Set<ToolType> getToolTypes() {
        clientCheck();
        return toolTypes;
    }

    @Nonnull
    @Override
    public Map<ToolType, Integer> getToolTypeLevel() {
        clientCheck();
        return toolTypeLevel;
    }

    @Override
    public float getDestroySpeed(@Nonnull BlockState state) {
        return getDestroySpeed();
    }

    @Override
    public float getDestroySpeed() {
        clientCheck();
        return destroySpeed;
    }

    @Override
    public boolean canDisableShield(ItemStack shield, LivingEntity entity, LivingEntity attacker) {
        return canDisableShield();
    }

    @Override
    public boolean canDisableShield() {
        clientCheck();
        return disableShield;
    }

    @Override
    public boolean isValidRepairItem(@Nonnull ItemStack repair) {
        for (Ingredient ingredient : getMaterial()) {
            if (ingredient.test(repair))
                return true;
        }

        return false;
    }

    @Override
    public NonNullList<Ingredient> getMaterial() {
        clientCheck();
        NonNullList<Ingredient> list = NonNullList.withSize(coreFlg.size(), Ingredient.EMPTY);

        for (int i = 0; i < coreFlg.size(); i++) {
            ItemStack module = getModule(coreFlg.getInt(i));
            Item item = module.getItem();

            if (item instanceof IToolModule)
                list.set(i, ((IToolModule) item).getMaterial(module));
        }

        return list;
    }

    @Override
    public int getMaxDurability() {
        clientCheck();
        return maxDurability;
    }

    @Override
    public int getMaxEnergyCapacity() {
        clientCheck();
        return maxEnergyCapacity;
    }

    @Override
    public double getDamage() {
        clientCheck();
        return damage;
    }

    @Override
    public double getAttackSpeed() {
        clientCheck();
        return attackSpeed;
    }

    @Override
    public int getEnchantability() {
        clientCheck();
        return enchantability;
    }

    @Override
    public float getXpRepairRatio() {
        clientCheck();
        return xpRepairRatio;
    }

    @Override
    public boolean isEnchantable(Set<Enchantment> enchantments) {
        clientCheck();

        for (EnchantmentType type : enchantmentTypes) {
            if (enchantments.stream().anyMatch(e -> e.category == type))
                return true;
        }

        for (Enchantment enchantment : this.enchantments) {
            if (enchantments.stream().anyMatch(e -> e == enchantment))
                return true;
        }

        return false;
    }

    @Override
    public boolean isEnchantable(Enchantment enchantment) {
        clientCheck();

        if (enchantmentTypes.contains(enchantment.category))
            return true;

        return enchantments.contains(enchantment);
    }

    @Override
    public boolean isFireImmune() {
        return fireImmune;
    }

    protected CompoundNBT serialize() {
        CompoundNBT capNBT = getCapNBT();
        ListNBT listNBT = new ListNBT();
        capNBT.put("module", listNBT);
        capNBT.putInt("size", getModules().size());
        capNBT.putBoolean("latest", false);
        capNBT.putIntArray("core", coreFlg);

        for (ItemStack module : getModules()) {
            listNBT.add(module.serializeNBT());
        }

        return capNBT;
    }

    protected void deserialize(CompoundNBT nbt) {
        if (!nbt.contains("module") || !nbt.contains("size")) {
            modules = NonNullList.withSize(3, ItemStack.EMPTY);
            return;
        }

        modules = NonNullList.withSize(nbt.getInt("size"), ItemStack.EMPTY);
        ListNBT list = nbt.getList("module", Constants.NBT.TAG_COMPOUND);

        for (int i = 0, listSize = list.size(); i < listSize; i++) {
            modules.set(i, ItemStack.of(list.getCompound(i)));
        }

        if (nbt.contains("core")) {
            coreFlg = new IntArrayList(nbt.getIntArray("core"));
        }
    }

    protected CompoundNBT getCapNBT() {
        CompoundNBT cap = stack.getOrCreateTagElement("cap");
        CompoundNBT nbt;

        if (!cap.contains("module"))
            cap.put("module", (nbt = new CompoundNBT()));
        else
            nbt = cap.getCompound("module");

        return nbt;
    }

    protected void setCapNBT(CompoundNBT nbt) {
        CompoundNBT cap = stack.getOrCreateTagElement("cap");
        cap.put("module", nbt);
    }

    protected void onModuleChanged(int slot) {
        CompoundNBT capNBT = getCapNBT();
        ListNBT listNBT;

        if (!capNBT.contains("module") || !capNBT.contains("size") || capNBT.getInt("size") != getModules().size()) {
            setCapNBT(serialize());
            return;
        } else
            listNBT = capNBT.getList("module", Constants.NBT.TAG_COMPOUND);

        listNBT.set(slot, getModule(slot).serializeNBT());
        reloadProperty();
    }

    protected void onModuleChanged() {
        setCapNBT(serialize());
        reloadProperty();
    }

    protected void onCoreChanged() {
        CompoundNBT capNBT = getCapNBT();
        capNBT.putIntArray("core", coreFlg);
        reloadCoreProperty();
    }

    protected void reloadCoreProperty() {
        toolTypeLevel = new HashMap<>();

        for (int i = 0; i < coreFlg.size(); i++) {
            ItemStack itemStack = modules.get(coreFlg.getInt(i));
            Item item = itemStack.getItem();

            if (!(item instanceof IToolModule)) {
                continue;
            }

            IToolModule tool = (IToolModule) item;
            List<Pair<ToolType, Integer>> toolType = tool.getToolType(itemStack);

            for (Pair<ToolType, Integer> pair : toolType) {
                if (toolTypeLevel.getOrDefault(pair.first, 0) < pair.second)
                    toolTypeLevel.put(pair.first, pair.second);
            }
        }

        toolTypes = toolTypeLevel.keySet();
    }

    protected void reloadProperty() {
        toolTypeLevel = null;
        toolTypes = null;
        disableShield = false;
        destroySpeed = 0;
        maxDurability = 0;
        maxEnergyCapacity = 0;
        damage = 0;
        attackSpeed = 0;
        enchantability = 0;
        enchantments = new HashSet<>();
        enchantmentTypes = new HashSet<>();
        fireImmune = false;
        xpRepairRatio = 0;

        HashMap<ToolType, Integer> map = new HashMap<>();

        for (int i = 0, modulesSize = modules.size(); i < modulesSize; i++) {
            ItemStack stack = modules.get(i);
            Item item = stack.getItem();

            if (!(item instanceof IToolModule))
                continue;

            IToolModule module = (IToolModule) item;

            //只读取核心模块的工具类型
            if (coreFlg.contains(i)) {
                for (Pair<ToolType, Integer> pair : module.getToolType(stack)) {
                    Integer level = map.getOrDefault(pair.first, 0);
                    //同种类型只保存等级最高的
                    if (level < pair.second)
                        map.put(pair.first, pair.second);
                }
            }

            //读取是否可破盾
            disableShield = module.canDisabledShield(stack) || disableShield;

            //读取挖掘速度
            destroySpeed += module.getDestroySpeed(stack);

            //读取最大耐久
            maxDurability += module.getDurability(stack);

            //读取最大能量
            maxEnergyCapacity += module.getEnergy(stack);

            //读取伤害值
            damage += module.getAttackDamage(stack);

            //读取攻击速度
            attackSpeed += module.getAttackSpeed(stack);

            //读取可用附魔
            enchantments.addAll(module.getEnchantable(stack));

            //附魔能力
            enchantability += module.getEnchantability(stack);

            //允许的附魔类型
            enchantmentTypes.addAll(module.getEnchantmentTypes(stack));

            //是否免疫火焰
            fireImmune = module.isFireImmune(stack) || fireImmune;

            //经验修补倍率
            xpRepairRatio += module.getXPRepairRatio(stack);
        }

        toolTypes = map.keySet();
        toolTypeLevel = map;
    }

    private void clientCheck() {
        if (EffectiveSide.get().isServer())
            return;

        CompoundNBT nbt = getCapNBT();

        if (!nbt.contains("latest") || nbt.getBoolean("latest"))
            return;

        reload();
        getCapNBT().putBoolean("latest", true);
    }

    protected void reload() {
        deserialize(getCapNBT());
        reloadProperty();
    }

    @Override
    public boolean format(@Nonnull IntArrayList coreFlg, @Nonnull NonNullList<ItemStack> modules, int size) {
        if (size < modules.size())
            this.modules = modules;
        else {
            this.modules = NonNullList.withSize(size, ItemStack.EMPTY);

            for (int i = 0, modulesSize = modules.size(); i < modulesSize; i++) {
                ItemStack module = modules.get(i);
                this.modules.set(i, module);
            }
        }

        this.coreFlg = coreFlg;
        setCapNBT(serialize());
        reloadProperty();
        return true;
    }
}
